#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""  
@Project : audit
@File : Nginx_rules.py
@Author : 陳
@Time : 2025/11/3 9:54  
@脚本说明 : 
"""

import subprocess
import re
from typing import Dict, List, Any
import logging

logger = logging.getLogger(__name__)


class NginxSecurityRules:
    """Nginx安全加固规则类"""

    @staticmethod
    def run_cmd_simple(command: str) -> str:
        """简化的命令执行，处理Linux编码问题"""
        try:
            result = subprocess.run(
                command,
                shell=True,
                capture_output=True,
                timeout=30,
                text=True
            )
            return result.stdout.strip() or result.stderr.strip()
        except Exception as e:
            return f"Error: {str(e)}"

    @staticmethod
    def find_nginx_config() -> str:
        """查找nginx主配置文件路径"""
        # 常见配置文件路径
        common_paths = [
            "/etc/nginx/nginx.conf",
            "/usr/local/nginx/conf/nginx.conf",
            "/etc/nginx/conf/nginx.conf"
        ]
        for path in common_paths:
            if NginxSecurityRules.run_cmd_simple(f"test -f {path} && echo 1 || echo 0") == "1":
                return path

        # 尝试通过nginx命令查找
        cmd_result = NginxSecurityRules.run_cmd_simple("nginx -V 2>&1 | grep -oP '(?<=--conf-path=).+'")
        if cmd_result and "Error" not in cmd_result:
            return cmd_result.strip()

        return ""

    @staticmethod
    def check_directory_listing() -> Dict[str, Any]:
        """1. 检查是否禁止目录列表"""
        print("[Nginx规则1] 检查目录列表禁止配置...")
        status_details = []
        issues = []
        config_path = NginxSecurityRules.find_nginx_config()
        has_problem = False

        if not config_path:
            issues.append("未找到Nginx配置文件")
            status_details.append("✗ 配置文件: 未找到nginx.conf")
            return {
                "rule_name": "禁止目录列表检查",
                "status": "未通过",
                "config_status": {"config_exists": False},
                "status_details": status_details,
                "recommendation": "请先确认Nginx配置文件位置"
            }

        # 检查主配置和虚拟主机配置
        all_configs = [config_path]
        include_dirs = re.findall(r'include\s+([^\s;]+);', NginxSecurityRules.run_cmd_simple(f"cat {config_path}"))
        for dir_path in include_dirs:
            if '*' in dir_path:
                all_configs.extend(NginxSecurityRules.run_cmd_simple(f"ls {dir_path} 2>/dev/null").splitlines())

        autoindex_off = 0
        autoindex_on = 0
        for conf in all_configs:
            if NginxSecurityRules.run_cmd_simple(f"test -f {conf} && echo 1 || echo 0") == "1":
                content = NginxSecurityRules.run_cmd_simple(f"cat {conf}")
                autoindex_on += len(re.findall(r'autoindex\s+on;', content))
                autoindex_off += len(re.findall(r'autoindex\s+off;', content))

        if autoindex_on > 0:
            has_problem = True
            issues.append(f"发现{autoindex_on}处开启目录列表配置")
            status_details.append(f"✗ 目录列表: 存在{autoindex_on}处开启配置")
        else:
            status_details.append(f"✓ 目录列表: 已禁用（检测到{autoindex_off}处关闭配置）")

        return {
            "rule_name": "禁止目录列表检查",
            "status": "通过" if not has_problem and not issues else "未通过",
            "config_status": {
                "config_exists": bool(config_path),
                "autoindex_on_count": autoindex_on,
                "autoindex_off_count": autoindex_off
            },
            "status_details": status_details,
            "recommendation": "目录列表已正确禁用" if not has_problem else f"需在所有location中设置autoindex off; 关闭{autoindex_on}处开启的配置"
        }

    @staticmethod
    def check_version_hiding() -> Dict[str, Any]:
        """2. 检查是否隐藏Nginx版本信息"""
        print("[Nginx规则2] 检查版本信息隐藏配置...")
        status_details = []
        issues = []
        config_path = NginxSecurityRules.find_nginx_config()
        has_problem = False

        if not config_path:
            issues.append("未找到Nginx配置文件")
            status_details.append("✗ 配置文件: 未找到nginx.conf")
            return {
                "rule_name": "隐藏版本信息检查",
                "status": "未通过",
                "config_status": {"config_exists": False},
                "status_details": status_details,
                "recommendation": "请先确认Nginx配置文件位置"
            }

        # 检查server_tokens配置
        content = NginxSecurityRules.run_cmd_simple(f"cat {config_path}")
        server_tokens_off = re.search(r'server_tokens\s+off;', content) is not None

        # 检查响应头（实际生效验证）
        port = re.search(r'listen\s+(\d+);', content)
        test_port = port.group(1) if port else "80"
        header_check = NginxSecurityRules.run_cmd_simple(
            f"curl -I http://localhost:{test_port} 2>/dev/null | grep 'Server: nginx'"
        )
        version_exposed = re.search(r'nginx/\d+\.\d+\.\d+', header_check) is not None

        if server_tokens_off and not version_exposed:
            status_details.append("✓ 版本信息: 已隐藏")
        else:
            has_problem = True
            if not server_tokens_off:
                issues.append("未配置server_tokens off;")
                status_details.append("✗ 配置检查: 缺少server_tokens off;")
            if version_exposed:
                issues.append("响应头仍暴露版本信息")
                status_details.append("✗ 响应头检查: 版本信息可见")

        return {
            "rule_name": "隐藏版本信息检查",
            "status": "通过" if not has_problem else "未通过",
            "config_status": {
                "server_tokens_off": server_tokens_off,
                "version_exposed": version_exposed
            },
            "status_details": status_details,
            "recommendation": "版本信息隐藏配置正确" if not has_problem else "需在http块添加server_tokens off; 并重启Nginx"
        }

    @staticmethod
    def check_minimal_permissions() -> Dict[str, Any]:
        """3. 检查最小化权限配置"""
        print("[Nginx规则3] 检查最小化权限配置...")
        status_details = []
        issues = []
        config_path = NginxSecurityRules.find_nginx_config()

        # 检查运行用户
        run_user = "nobody"
        if config_path:
            content = NginxSecurityRules.run_cmd_simple(f"cat {config_path}")
            user_match = re.search(r'user\s+(\w+);', content)
            if user_match:
                run_user = user_match.group(1)
            else:
                issues.append("未明确配置运行用户")
                status_details.append("✗ 运行用户: 未明确配置（使用默认值）")

        # 检查用户是否为非root
        if run_user == "root":
            issues.append("Nginx使用root用户运行，存在高风险")
            status_details.append("✗ 运行用户: 正在使用root用户（高风险）")
        else:
            status_details.append(f"✓ 运行用户: 使用{run_user}非root用户")

        # 检查网站根目录权限
        web_root = "/usr/share/nginx/html"  # 默认路径
        if config_path:
            content = NginxSecurityRules.run_cmd_simple(f"cat {config_path}")
            root_match = re.search(r'root\s+([^;]+);', content)
            if root_match:
                web_root = root_match.group(1)

        if NginxSecurityRules.run_cmd_simple(f"test -d {web_root} && echo 1 || echo 0") == "1":
            perm_check = NginxSecurityRules.run_cmd_simple(f"stat -c '%a %U' {web_root}")
            permissions, owner = perm_check.split() if ' ' in perm_check else ("", "")

            if permissions and int(permissions) > 755:
                issues.append(f"网站根目录权限过高({permissions})")
                status_details.append(f"✗ 目录权限: {web_root} 权限为{permissions}（建议≤755）")
            else:
                status_details.append(f"✓ 目录权限: {web_root} 权限为{permissions}")

            if owner == "root" and run_user != "root":
                issues.append(f"网站根目录所有者为root，与运行用户{run_user}不一致")
                status_details.append(f"✗ 目录所有者: {web_root} 所有者为root（建议改为{run_user}）")
            else:
                status_details.append(f"✓ 目录所有者: {web_root} 所有者为{owner}")
        else:
            issues.append(f"网站根目录{web_root}不存在")
            status_details.append(f"✗ 目录检查: {web_root} 不存在")

        return {
            "rule_name": "最小化权限检查",
            "status": "通过" if not issues else "未通过",
            "config_status": {
                "run_user": run_user,
                "is_root": run_user == "root",
                "web_root": web_root,
                "web_root_permissions": permissions if 'permissions' in locals() else ""
            },
            "status_details": status_details,
            "recommendation": "权限配置符合最小化原则" if not issues else f"需修复: {'; '.join(issues)}"
        }

    @staticmethod
    def check_log_audit() -> Dict[str, Any]:
        """4. 检查日志审计配置"""
        print("[Nginx规则4] 检查日志审计配置...")
        status_details = []
        issues = []
        config_path = NginxSecurityRules.find_nginx_config()
        has_access_log = False
        has_error_log = False

        if not config_path:
            issues.append("未找到Nginx配置文件")
            status_details.append("✗ 配置文件: 未找到nginx.conf")
            return {
                "rule_name": "日志审计检查",
                "status": "未通过",
                "config_status": {"config_exists": False},
                "status_details": status_details,
                "recommendation": "请先确认Nginx配置文件位置"
            }

        # 检查日志配置
        content = NginxSecurityRules.run_cmd_simple(f"cat {config_path}")
        access_log_match = re.search(r'access_log\s+(\S+)\s+', content)
        error_log_match = re.search(r'error_log\s+(\S+)\s+', content)

        if access_log_match:
            has_access_log = True
            access_log_path = access_log_match.group(1)
            log_exists = NginxSecurityRules.run_cmd_simple(f"test -f {access_log_path} && echo 1 || echo 0") == "1"
            status_details.append(f"✓ 访问日志: 配置路径 {access_log_path}（{'存在' if log_exists else '不存在'}）")
        else:
            issues.append("未配置访问日志")
            status_details.append("✗ 访问日志: 未配置")

        if error_log_match:
            has_error_log = True
            error_log_path = error_log_match.group(1)
            log_exists = NginxSecurityRules.run_cmd_simple(f"test -f {error_log_path} && echo 1 || echo 0") == "1"
            status_details.append(f"✓ 错误日志: 配置路径 {error_log_path}（{'存在' if log_exists else '不存在'}）")
        else:
            issues.append("未配置错误日志")
            status_details.append("✗ 错误日志: 未配置")

        # 检查日志轮转
        logrotate_check = NginxSecurityRules.run_cmd_simple("grep 'nginx' /etc/logrotate.d/nginx 2>/dev/null")
        if logrotate_check:
            status_details.append("✓ 日志轮转: 已配置")
        else:
            issues.append("未配置日志轮转策略")
            status_details.append("✗ 日志轮转: 未配置（可能导致日志文件过大）")

        return {
            "rule_name": "日志审计检查",
            "status": "通过" if not issues else "未通过",
            "config_status": {
                "access_log_enabled": has_access_log,
                "error_log_enabled": has_error_log,
                "logrotate_enabled": bool(logrotate_check)
            },
            "status_details": status_details,
            "recommendation": "日志审计配置完整" if not issues else f"需修复: {'; '.join(issues)}"
        }

    @staticmethod
    def check_data_backup() -> Dict[str, Any]:
        """5. 检查重要数据备份策略"""
        print("[Nginx规则5] 检查数据备份策略...")
        status_details = []
        issues = []

        # 常见网站目录
        web_dirs = [
            "/usr/share/nginx/html",
            "/var/www/html",
            "/etc/nginx/conf.d"
        ]

        # 检查备份文件或目录
        backup_signs = []
        for dir_path in web_dirs:
            if NginxSecurityRules.run_cmd_simple(f"test -d {dir_path} && echo 1 || echo 0") == "1":
                # 检查最近7天的备份
                backups = NginxSecurityRules.run_cmd_simple(
                    f"find {dir_path} ../$(basename {dir_path})_backup* -maxdepth 0 -type d -mtime -7 2>/dev/null"
                )
                if backups:
                    backup_signs.extend(backups.splitlines())

        # 检查备份脚本
        backup_scripts = NginxSecurityRules.run_cmd_simple(
            "grep -r 'nginx' /etc/cron* /var/spool/cron 2>/dev/null | grep -E 'tar|cp|rsync'"
        )

        if backup_signs or backup_scripts:
            status_details.append("✓ 备份状态: 存在近期备份或备份脚本")
            if backup_signs:
                status_details.append(f"  最近备份: {backup_signs[0]}")
            if backup_scripts:
                status_details.append(f"  备份脚本: 存在定时任务配置")
        else:
            issues.append("未发现近期备份或自动备份策略")
            status_details.append("✗ 备份状态: 无有效备份机制")

        return {
            "rule_name": "重要数据备份检查",
            "status": "通过" if not issues else "未通过",
            "config_status": {
                "has_recent_backup": bool(backup_signs),
                "has_backup_script": bool(backup_scripts)
            },
            "status_details": status_details,
            "recommendation": "备份策略配置完善" if not issues else "建议配置定时备份网站目录和Nginx配置文件"
        }

    @staticmethod
    def check_maintenance_patches() -> Dict[str, Any]:
        """6. 检查定期维护和补丁策略"""
        print("[Nginx规则6] 检查维护和补丁策略...")
        status_details = []
        issues = []

        # 检查当前版本
        version_output = NginxSecurityRules.run_cmd_simple("nginx -v 2>&1")
        version_match = re.search(r'nginx/(\d+\.\d+\.\d+)', version_output)
        current_version = version_match.group(1) if version_match else "未知"
        status_details.append(f"✓ 当前版本: {current_version}")

        # 检查是否有可用更新
        update_check = NginxSecurityRules.run_cmd_simple(
            "apt list --upgradable nginx 2>/dev/null | grep -v 'Listing...' || yum check-update nginx 2>/dev/null | grep nginx"
        )
        if update_check:
            issues.append("存在可用的Nginx更新")
            status_details.append("✗ 版本更新: 发现可用更新")
        else:
            status_details.append("✓ 版本更新: 当前为最新版本")

        # 检查自动更新配置
        auto_update = NginxSecurityRules.run_cmd_simple(
            "grep -r 'nginx' /etc/apt/apt.conf.d/ /etc/cron.daily/ 2>/dev/null | grep -i 'upgrade'"
        )
        if auto_update:
            status_details.append("✓ 自动更新: 已配置")
        else:
            issues.append("未配置自动更新策略")
            status_details.append("✗ 自动更新: 未配置定期更新")

        return {
            "rule_name": "维护和补丁策略检查",
            "status": "通过" if not issues else "未通过",
            "config_status": {
                "current_version": current_version,
                "has_updates": bool(update_check),
                "auto_update_enabled": bool(auto_update)
            },
            "status_details": status_details,
            "recommendation": "维护策略完善" if not issues else f"需修复: {'; '.join(issues)}"
        }

    @staticmethod
    def get_all_rules() -> List[callable]:
        """获取所有Nginx审计规则的函数列表"""
        return [
            NginxSecurityRules.check_directory_listing,
            NginxSecurityRules.check_version_hiding,
            NginxSecurityRules.check_minimal_permissions,
            NginxSecurityRules.check_log_audit,
            NginxSecurityRules.check_data_backup,
            NginxSecurityRules.check_maintenance_patches
        ]